package node

import (
	"math/rand"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/libp2p/go-libp2p-core/helpers"
	"hundsun.com/hsl/hschain/p2p/common/p2perror"
	"hundsun.com/hsl/hschain/store/mq"

	"github.com/libp2p/go-libp2p-core/network"
	multiCodec "github.com/multiformats/go-multicodec/protobuf"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// ReadStream reads message from stream.
func ReadStream(data proto.Message, stream network.Stream) error {
	decoder := multiCodec.Multicodec(nil).Decoder(stream)
	err := decoder.Decode(data)
	if err != nil {
		return p2perror.Errorf("ReadStream pid:%s protocolID:%v failed, reason:%v", stream.Conn().RemotePeer().Pretty(), stream.Protocol(), err)
	}
	return nil
}

// WriteStream writes message to stream.
func WriteStream(data proto.Message, stream network.Stream) error {
	enc := multiCodec.Multicodec(nil).Encoder(stream)
	err := enc.Encode(data)
	if err != nil {
		return p2perror.Errorf("WriteStream pid:%s protocolID:%v failed, reason:%v", stream.Conn().RemotePeer().Pretty(), stream.Protocol(), err)
	}
	return nil
}

// CloseStream closes the stream after writing, and waits for the EOF.
func CloseStream(stream network.Stream) {
	if stream == nil {
		return
	}
	go func() {
		err := helpers.FullClose(stream)
		if err != nil {
			//just log it because it dose not matter
			p2perror.Log().Debugf("CloseStream, failed, reason:%v", err)
		}
	}()
}

// HandlerWithClose wraps handler with closing stream and recovering from panic.
func HandlerWithClose(f network.StreamHandler) network.StreamHandler {
	return func(stream network.Stream) {
		defer func() {
			if r := recover(); r != nil {
				p2perror.Log().Error("handle stream", "panic error", r)
				_ = stream.Reset()
			}
		}()
		f(stream)
		CloseStream(stream)
	}
}

// EventHandlerWithRecover warps the event handler with recover for catching the panic while processing message.
func EventHandlerWithRecover(f func(m *mq.Message)) func(m *mq.Message) {
	return func(m *mq.Message) {
		defer func() {
			if r := recover(); r != nil {
				p2perror.Log().Errorf("handle event panic, reason:%v", r)
			}
		}()
		f(m)
	}
}
